package org.chatty.applet;

import java.applet.Applet;

import java.awt.BorderLayout;
import java.awt.Color;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.URL;
import java.net.URLConnection;

import java.util.ResourceBundle;

import java.security.AccessController;
import java.security.PrivilegedAction;

import javax.sound.sampled.AudioPermission;
import javax.sound.sampled.AudioFileFormat;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;

import javax.swing.SwingUtilities;

import org.apache.commons.codec.binary.Base64;

import org.chatty.Player;
import org.chatty.Poster;
import org.chatty.Recorder;
import org.chatty.RecordingSetup;
import org.chatty.Saver;
import org.chatty.ThreadedByter;

public class RecordingApplet extends Applet {

	private Recorder recorder_;
	private Player player_;
	private Poster poster_;

	private String postURL_;

	JButton buttons[] = null;
	private JButton recordingButton_;
	private JButton playBackButton_;
	private JButton saveButton_;
	private JTextArea comment_;

	private byte[] audioData_;
	private boolean encode_ = true;

	static ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle( "messages" );

	public final static String RECORD_KEY =  "recordingApplet.record";
	public final static String STOP_KEY   =  "recordingApplet.stop";
	public final static String PLAY_KEY   =  "recordingApplet.play";
	public final static String SAVE_KEY   =  "recordingApplet.save";

	public void inidt() {
	}

	public void init() {
		try {
			
			// check the permissions straight away...
			System.getSecurityManager().checkPermission( new AudioPermission( "play" ) );
			System.getSecurityManager().checkPermission( new AudioPermission( "record" ) );

			if ( null != super.getParameter( "encode" ) ) {
				if ( "false".equals( super.getParameter( "encode" ) ) ) {
					this.setEncode( false );
				}
			}

			this.setPostURL( super.getParameter( "postURL" ) );
			System.out.println( "nice " + this.getPostURL() );

			// java.security.AccessController from:
			// http://faq.javaranch.com/java/HowCanAnAppletReadFilesOnTheLocalFileSystem

			final Recorder recorder = ( Recorder ) AccessController.doPrivileged(
				new PrivilegedAction() {
					public Object run() { 
						try {
							return new Recorder();
						} catch ( Exception e ) {
							throw new RuntimeException( e );
						}
					}
				}
			);
			recorder.setCallBack( 
				new ThreadedByter.CallBack() {
					public void progress( int count, byte audioData[] ) {
						recording_progress( count, audioData );
					}
					public void finished( byte audioData[] ) {
						recording_finished( audioData );
					}
					public void error( Exception exception ) {
						recording_error( exception );
					}
				}
			);

			Player player = ( Player ) AccessController.doPrivileged(
				new PrivilegedAction() {
					public Object run() {
						try { 
							return new Player( recorder.getRecordingSetup().getAudioFormat() );
						} catch ( Exception e ) {
							throw new RuntimeException( e );
						}
					}
				}
			);
			player.setCallBack( 
				new ThreadedByter.CallBack() {
					public void progress( int count, byte audioData[] ) {
						playing_progress( count, audioData );
					}
					public void finished( byte audioData[] ) {
						playing_finished( audioData );
					}
					public void error( Exception exception ) {
						playing_error( exception );
					}
				}
			);

			Poster poster = new Poster(
				  recorder.getRecordingSetup().getAudioFormat()
				, AudioFileFormat.Type.WAVE 
				, this.getPostURL()
				, this.getEncode()
			);
			poster.setCallBack(
				new ThreadedByter.CallBack() {
					public void progress( int count, byte audioData[] ) {
						posting_progress( count, audioData );
					}
					public void finished( byte audioData[] ) {
						posting_finished( audioData );
					}
					public void error( Exception exception ) {
						posting_error( exception );
					}
				}
			);

			this.setRecorder( recorder );
			this.setPlayer( player );
			this.setPoster( poster );
			this.gui();
		} catch ( Exception exception ) {
			Throwable boohoo = exception;
			if ( exception instanceof RuntimeException ) { 
				boohoo = exception.getCause();
			}
			super.showStatus( boohoo.toString() );
			boohoo.printStackTrace();

			JPanel jpanel = new JPanel();
			jpanel.add( new JLabel( boohoo.toString() ) );
			this.add( jpanel );
		}
	}
	
	//// thread callback methods

	public void recording_progress( int count, byte audioData[] ) {
		// TODO: some status showing thing
		super.showStatus( "recording " + System.currentTimeMillis() );
	}

	public void recording_finished( byte audioData[] ) {
		this.getRecordingButton().setText( this.msg( RECORD_KEY ) );
		this.setAudioData( audioData );
		this.getPlayBackButton().setEnabled( true );
		this.getSaveButton().setEnabled( true );
		super.showStatus( "finished recording " );
	}

	public void recording_error( Exception exception ) {
		// TODO: handle error properly...
		super.showStatus( exception.toString() );
	}

	public void playing_progress( int count, byte audioData[] ) {
		// TODO: some status showing thing
		super.showStatus( "playing " + System.currentTimeMillis() );
    }

	public void playing_finished( byte audioData[] ) {
		for ( JButton button : buttons ) {
			button.setEnabled( true );
		}
		super.showStatus( "finished playing" );
	}

	public void playing_error( Exception exception ) {
		// TODO: handle error properly...
		super.showStatus( exception.toString() );
		playing_finished( null );
	}

	public void posting_progress( int count, byte audioData[] ) {
		// TODO: some status showing thing
		System.out.println( "posted " + count );
		super.showStatus( "posting "  + System.currentTimeMillis() );
    }

	public void posting_finished( byte audioData[] ) {
		this.getRecordingButton().setEnabled( true );
		super.showStatus( "finished posting" );
		// TODO: ajax + http://java.sun.com/products/plugin/1.3/docs/jsobject.html
		try {
			super.getAppletContext().showDocument( new URL( this.getParameter( "refreshURL" ) ) );
		} catch( Exception exception ) {
			super.showStatus( exception.toString() );
		}
	}

	public void posting_error( Exception exception ) {
		// TODO: handle error properly...
		super.showStatus( exception.toString() );
		exception.printStackTrace();
		posting_finished( null );
	}

	//// ActionEvent handlers

	public void recordingAction( ActionEvent e ) {
		if ( e.getActionCommand().equals( this.msg( RECORD_KEY ) ) ) {
			this.getRecordingButton().setText( this.msg( STOP_KEY ) );
			this.getPlayBackButton().setEnabled( false );
			this.getSaveButton().setEnabled( false );
			this.setAudioData( null );
			this.getRecorder().start();
		} else {
			this.getRecorder().stop();
		}
	}

	public void playingAction( ActionEvent e ) {
		if ( null != this.getAudioData() ) {
			this.disableAllButtons();
			this.getPlayer().setup( this.getAudioData() );
			this.getPlayer().start();
		}
	}

	// TODO: this should be run in a different thread...
	public void savingAction( ActionEvent e ) {
		if ( null != this.getAudioData() ) {
			this.disableAllButtons();
			this.getPoster().setup( this.getAudioData(), this.getComment().getText() );
			this.getPoster().start();
		}
	}

	public void disableAllButtons() {
		for ( JButton button : this.buttons ) {
			button.setEnabled( false );
		}
	}

	////

	//http://forums.java.net/jive/thread.jspa?messageID=353667
	public RecordingApplet that() {
		return this;
	}

/* TODO: make this junk work...
	boolean playing_hack = false;
	public synchronized void play( String href ) {
		if ( !playing_hack ) {
			playing_hack = true;
			for ( JButton button : buttons ) {
				button.setEnabled( false );
			}

			this.getPlayer().recorderCallBack( this.getAudioData() );

			try {
				super.getAudioClip( new URL( href ) ).play();
			} catch ( Exception e ) {
				super.showStatus( e.toString() );
			}

			for ( JButton button : buttons ) {
				button.setEnabled( true );
			}
			playing_hack = false;
		}
	}
*/

	//// 

	public void gui() {
		this.setRecordingButton( new JButton( this.msg( RECORD_KEY ) ) );
		this.setPlayBackButton(  new JButton( this.msg( PLAY_KEY   ) ) );
		this.setSaveButton(      new JButton( this.msg( SAVE_KEY   ) ) );
		this.setComment(         new JTextArea( 2, 40 ) );

		this.getComment().setBorder( BorderFactory.createLineBorder( Color.black ) );

		this.getRecordingButton().setEnabled( null != this.getRecorder() );
		this.getPlayBackButton().setEnabled( false );
		this.getSaveButton().setEnabled( false );

		this.getRecordingButton().addActionListener(
			new ActionListener(){
				public void actionPerformed( ActionEvent e ) {
					recordingAction( e );
				}
			}
		);

		this.getPlayBackButton().addActionListener(
			new ActionListener(){
				public void actionPerformed( ActionEvent e ) {
					playingAction( e );
				}
			}
		);

		this.getSaveButton().addActionListener(
			new ActionListener(){
				public void actionPerformed( ActionEvent e ) {
					savingAction( e );
				}
			}
		);

		this.buttons = new JButton[] {
			  this.getRecordingButton()
			, this.getPlayBackButton()
			, this.getSaveButton()
		};
		JPanel panel_buttons = new JPanel();

		for ( JButton button : this.buttons ) {
			panel_buttons.add( button );
		}

		JPanel panel_all = new JPanel( new BorderLayout() );

		panel_all.add( panel_buttons, BorderLayout.SOUTH );
		panel_all.add( this.getComment(), BorderLayout.NORTH );
		this.add( panel_all );

		SwingUtilities.invokeLater( 
			new Runnable() { public void run() { getComment().requestFocus(); } } 
		);
	}

	//// getters and setters 

	public Recorder getRecorder() {
		return this.recorder_;
	}

	public void setRecorder( Recorder recorder ) {
		this.recorder_ = recorder;
	}

	public Player getPlayer() {
		return this.player_;
	}

	public void setPlayer( Player player ) {
		this.player_ = player;
	}

	public JButton getRecordingButton() {
		return this.recordingButton_;
	}

	public void setRecordingButton( JButton recordingButton ) {
		this.recordingButton_ = recordingButton;
	}

	public JButton getPlayBackButton() {
		return this.playBackButton_;
	}

	public void setPlayBackButton( JButton playBackButton ) {
		this.playBackButton_ = playBackButton;
	}

	public JButton getSaveButton() {
		return this.saveButton_;
	}

	public void setSaveButton( JButton saveButton ) {
		this.saveButton_ = saveButton;
	}

	public byte[] getAudioData() {
		return this.audioData_;
	}

	public void setAudioData( byte[] audioData ) {
		this.audioData_ = audioData;
	}

	public String getPostURL() {
		return this.postURL_;
	}

	public void setPostURL( String postURL ) {
		this.postURL_ = postURL;
	}

	public boolean getEncode() {
		return this.encode_;
	}

	public void setEncode( boolean encode ) {
		this.encode_ = encode;
	}

	public Poster getPoster() {
		return this.poster_;
	}

	public void setPoster( Poster poster ) {
		this.poster_ = poster;
	}

	public JTextArea getComment() {
		return this.comment_;
	}

	public void setComment( JTextArea comment ) {
		this.comment_ = comment;
	}

	////

	public String msg( String key ) {
		return RESOURCE_BUNDLE.getString( key );
	}

};
